// call、apply 及 bind 函数
/**
 * 都是改变 this 的指向
 * bind 函数不自动执行， call 和 apply 会自动执行
 * call 和 apply 的区别是第二个参数不同
 */

Function.prototype.myCall = function (context) {
    if (typeof context === undefined || typeof context === null) {
        constex = window
    }

    const symbol = Symbol()
    context[symbol] = this
    const args = [...arguments].slice(1)
    const result = context[symbol](...args)
    delete context[symbol]

    return result
}

/**
 * 首先 context 为可选参数，如果不传的话默认上下文为 window
 * 接下来我们通过 Symbol 为 context 创建一个属性，并将值设置为需要调用的函数
 * 因为 call 可以传入多个参数作为调用函数的参数，所以需要将参数剥离出来
 * 然后调用函数并将对象上的函数删除
 */

Function.prototype.myApply = function (context) {
    if (context === undefined || context === null) {
        context = window
    }

    const symbol = Symbol()
    context[symbol] = this
    let result
    // 处理参数和 call 有区别
    if (arguments[1]) {
        result = context[symbol](...arguments[1])
    } else {
        result = context[symbol]()
    }
    delete context[symbol]

    return result
}

Function.prototype.myBind = function (context) {
    if (context === undefined || context === null) {
        context = window
    }

    const that = this
    const args = [...arguments].slice(1)
    //返回一个函数
    return function F() {
        // 因为返回了函数，我们可以 new F() ，所以需要判断
        if (this instanceof F) {
            return new that(...args, arguments)
        }
        return that.apply(context, args.concat(arguments))
    }
}

/**
 * bind 返回了一个函数，对于函数来说有两种方式调用，一种是直接调用，一种是通过 new 的方式，我们先来说直接调用的方式
 * 
 * 对于直接调用来说，这里选择了 apply 的方式实现，但是对于参数需要注意以下情况：
 * 因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2)，所以我们需要将两边的参数拼接起来，于是就有了这样的实现 args.concat(...arguments)
 * 
 * 最后来说通过 new 的方式，在之前的章节中我们学习过如何判断 this，对于 new 的情况来说，不会被任何方式改变 this，所以对于这种情况我们需要忽略传入的 this
 */